 arch/arm/boot/dts/armada-38x.dtsi |  16 +-
 drivers/gpio/gpio-mvebu.c         | 369 +++++++++++++++++++++++++++--
 2 files changed, 367 insertions(+), 18 deletions(-)

diff --git a/arch/arm/boot/dts/armada-38x.dtsi b/arch/arm/boot/dts/armada-38x.dtsi
index e4e1546..5894e45 100644
--- a/arch/arm/boot/dts/armada-38x.dtsi
+++ b/arch/arm/boot/dts/armada-38x.dtsi
@@ -339,31 +339,39 @@
 			};
 
 			gpio0: gpio@18100 {
-				compatible = "marvell,orion-gpio";
-				reg = <0x18100 0x40>;
+				compatible = "marvell,armada-370-gpio",
+					     "marvell,orion-gpio";
+				reg = <0x18100 0x40>, <0x181c0 0x08>;
+				reg-names = "gpio", "pwm";
 				ngpios = <32>;
 				gpio-controller;
 				#gpio-cells = <2>;
+				#pwm-cells = <2>;
 				interrupt-controller;
 				#interrupt-cells = <2>;
 				interrupts = <GIC_SPI 53 IRQ_TYPE_LEVEL_HIGH>,
 					     <GIC_SPI 54 IRQ_TYPE_LEVEL_HIGH>,
 					     <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>,
 					     <GIC_SPI 56 IRQ_TYPE_LEVEL_HIGH>;
+				clocks = <&coreclk 0>;
 			};
 
 			gpio1: gpio@18140 {
-				compatible = "marvell,orion-gpio";
-				reg = <0x18140 0x40>;
+				compatible = "marvell,armada-370-gpio",
+					     "marvell,orion-gpio";
+				reg = <0x18140 0x40>, <0x181c8 0x08>;
+				reg-names = "gpio", "pwm";
 				ngpios = <28>;
 				gpio-controller;
 				#gpio-cells = <2>;
+				#pwm-cells = <2>;
 				interrupt-controller;
 				#interrupt-cells = <2>;
 				interrupts = <GIC_SPI 58 IRQ_TYPE_LEVEL_HIGH>,
 					     <GIC_SPI 59 IRQ_TYPE_LEVEL_HIGH>,
 					     <GIC_SPI 60 IRQ_TYPE_LEVEL_HIGH>,
 					     <GIC_SPI 61 IRQ_TYPE_LEVEL_HIGH>;
+				clocks = <&coreclk 0>;
 			};
 
 			system-controller@18200 {
diff --git a/drivers/gpio/gpio-mvebu.c b/drivers/gpio/gpio-mvebu.c
index ffea532..9fe65f7 100644
--- a/drivers/gpio/gpio-mvebu.c
+++ b/drivers/gpio/gpio-mvebu.c
@@ -42,29 +42,43 @@
 #include <linux/io.h>
 #include <linux/of_irq.h>
 #include <linux/of_device.h>
+#include <linux/pwm.h>
 #include <linux/clk.h>
 #include <linux/pinctrl/consumer.h>
 #include <linux/irqchip/chained_irq.h>
+#include <linux/platform_device.h>
+
+#include "gpiolib.h"
 
 /*
  * GPIO unit register offsets.
  */
-#define GPIO_OUT_OFF		0x0000
-#define GPIO_IO_CONF_OFF	0x0004
-#define GPIO_BLINK_EN_OFF	0x0008
-#define GPIO_IN_POL_OFF		0x000c
-#define GPIO_DATA_IN_OFF	0x0010
-#define GPIO_EDGE_CAUSE_OFF	0x0014
-#define GPIO_EDGE_MASK_OFF	0x0018
-#define GPIO_LEVEL_MASK_OFF	0x001c
+#define GPIO_OUT_OFF			0x0000
+#define GPIO_IO_CONF_OFF		0x0004
+#define GPIO_BLINK_EN_OFF		0x0008
+#define GPIO_IN_POL_OFF			0x000c
+#define GPIO_DATA_IN_OFF		0x0010
+#define GPIO_EDGE_CAUSE_OFF		0x0014
+#define GPIO_EDGE_MASK_OFF		0x0018
+#define GPIO_LEVEL_MASK_OFF		0x001c
+#define GPIO_BLINK_CNT_SELECT_OFF	0x0020
+
+/*
+ * PWM register offsets.
+ */
+#define PWM_BLINK_ON_DURATION_OFF	0x0
+#define PWM_BLINK_OFF_DURATION_OFF	0x4
+
 
 /* The MV78200 has per-CPU registers for edge mask and level mask */
 #define GPIO_EDGE_MASK_MV78200_OFF(cpu)	  ((cpu) ? 0x30 : 0x18)
 #define GPIO_LEVEL_MASK_MV78200_OFF(cpu)  ((cpu) ? 0x34 : 0x1C)
 
-/* The Armada XP has per-CPU registers for interrupt cause, interrupt
+/*
+ * The Armada XP has per-CPU registers for interrupt cause, interrupt
  * mask and interrupt level mask. Those are relative to the
- * percpu_membase. */
+ * percpu_membase.
+ */
 #define GPIO_EDGE_CAUSE_ARMADAXP_OFF(cpu) ((cpu) * 0x4)
 #define GPIO_EDGE_MASK_ARMADAXP_OFF(cpu)  (0x10 + (cpu) * 0x4)
 #define GPIO_LEVEL_MASK_ARMADAXP_OFF(cpu) (0x20 + (cpu) * 0x4)
@@ -75,6 +89,41 @@
 
 #define MVEBU_MAX_GPIO_PER_BANK		32
 
+enum mvebu_pwm_ctrl {
+	MVEBU_PWM_CTRL_SET_A = 0,
+	MVEBU_PWM_CTRL_SET_B,
+	MVEBU_PWM_CTRL_MAX
+};
+
+struct mvebu_pwmchip {
+	void __iomem		*membase;
+	unsigned long		 clk_rate;
+	spinlock_t		 lock;
+	bool			 in_use;
+
+	/* Used to preserve GPIO/PWM registers across suspend/resume */
+	u32			 blink_on_duration;
+	u32			 blink_off_duration;
+};
+
+struct mvebu_pwm_chip_drv {
+	enum mvebu_pwm_ctrl	ctrl;
+	struct gpio_desc	*gpiod;
+	bool			master;
+};
+
+struct mvebu_pwm {
+	struct pwm_chip		 chip;
+	struct mvebu_gpio_chip	*mvchip;
+	struct mvebu_pwmchip     controller;
+	enum mvebu_pwm_ctrl	 default_counter;
+
+	/* Used to preserve GPIO/PWM registers across suspend/resume */
+	u32			 blink_select;
+};
+
+static struct mvebu_pwmchip	*mvebu_pwm_list[MVEBU_PWM_CTRL_MAX];
+
 struct mvebu_gpio_chip {
 	struct gpio_chip   chip;
 	spinlock_t	   lock;
@@ -84,6 +133,10 @@ struct mvebu_gpio_chip {
 	struct irq_domain *domain;
 	int		   soc_variant;
 
+	/* Used for PWM support */
+	struct clk	  *clk;
+	struct mvebu_pwm  *mvpwm;
+
 	/* Used to preserve GPIO registers across suspend/resume */
 	u32                out_reg;
 	u32                io_conf_reg;
@@ -102,6 +155,11 @@ static inline void __iomem *mvebu_gpioreg_out(struct mvebu_gpio_chip *mvchip)
 	return mvchip->membase + GPIO_OUT_OFF;
 }
 
+static void __iomem *mvebu_gpioreg_blink_select(struct mvebu_gpio_chip *mvchip)
+{
+	return mvchip->membase + GPIO_BLINK_CNT_SELECT_OFF;
+}
+
 static inline void __iomem *mvebu_gpioreg_blink(struct mvebu_gpio_chip *mvchip)
 {
 	return mvchip->membase + GPIO_BLINK_EN_OFF;
@@ -182,6 +240,20 @@ static void __iomem *mvebu_gpioreg_level_mask(struct mvebu_gpio_chip *mvchip)
 }
 
 /*
+ * Functions returning addresses of individual registers for a given
+ * PWM controller.
+ */
+static void __iomem *mvebu_pwmreg_blink_on_duration(struct mvebu_pwmchip *mvpwm)
+{
+	return mvpwm->membase + PWM_BLINK_ON_DURATION_OFF;
+}
+
+static void __iomem *mvebu_pwmreg_blink_off_duration(struct mvebu_pwmchip *mvpwm)
+{
+	return mvpwm->membase + PWM_BLINK_OFF_DURATION_OFF;
+}
+
+/*
  * Functions implementing the gpio_chip methods
  */
 
@@ -489,6 +561,262 @@ static void mvebu_gpio_irq_handler(struct irq_desc *desc)
 	chained_irq_exit(chip, desc);
 }
 
+/*
+ * Functions implementing the pwm_chip methods
+ */
+static struct mvebu_pwm *to_mvebu_pwm(struct pwm_chip *chip)
+{
+	return container_of(chip, struct mvebu_pwm, chip);
+}
+
+static int mvebu_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct mvebu_pwm *mvpwm = to_mvebu_pwm(chip);
+	struct mvebu_gpio_chip *mvchip = mvpwm->mvchip;
+	struct gpio_desc *desc;
+	enum mvebu_pwm_ctrl id;
+	int ret = 0;
+	struct mvebu_pwm_chip_drv *chip_data;
+	u32 u;
+
+	spin_lock(&mvpwm->controller.lock);
+
+	mvchip->blink_en_reg = readl_relaxed(mvebu_gpioreg_blink(mvchip));
+	if (pwm->chip_data || (mvchip->blink_en_reg & BIT(pwm->hwpwm)))
+		return -EBUSY;
+
+	desc = gpio_to_desc(mvchip->chip.base + pwm->hwpwm);
+	if (!desc) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	ret = gpiod_request(desc, "mvebu-pwm");
+	if (ret)
+		goto out;
+
+	ret = gpiod_direction_output(desc, 0);
+	if (ret) {
+		gpiod_free(desc);
+		goto out;
+	}
+
+	chip_data = kzalloc(sizeof(struct mvebu_pwm_chip_drv), GFP_KERNEL);
+	if (!chip_data) {
+		gpiod_free(desc);
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	for (id = MVEBU_PWM_CTRL_SET_A;id < MVEBU_PWM_CTRL_MAX; id++) {
+		if (!mvebu_pwm_list[id]->in_use) {
+			chip_data->ctrl   = id;
+			chip_data->master = true;
+			mvebu_pwm_list[id]->in_use = true;
+			break;
+		}
+	}
+
+	if (!chip_data->master)
+		chip_data->ctrl = mvpwm->default_counter;
+
+	u = readl_relaxed(mvebu_gpioreg_blink_select(mvchip));
+	if (id)
+		u |= (1 << pwm->hwpwm);
+	else
+		u &= ~(1 << pwm->hwpwm);
+	writel_relaxed(u, mvebu_gpioreg_blink_select(mvchip));
+
+	chip_data->gpiod = desc;
+	pwm->chip_data = chip_data;
+	mvpwm->blink_select = u;
+out:
+	spin_unlock(&mvpwm->controller.lock);
+	return ret;
+}
+
+static void mvebu_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct mvebu_pwm *mvpwm = to_mvebu_pwm(chip);
+	struct mvebu_pwm_chip_drv *chip_data = (struct mvebu_pwm_chip_drv*) pwm->chip_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&mvpwm->controller.lock, flags);
+	if (chip_data->master)
+		mvebu_pwm_list[chip_data->ctrl]->in_use = false;
+
+	gpiod_free(chip_data->gpiod);
+	kfree(chip_data);
+	pwm->chip_data = NULL;
+	spin_unlock_irqrestore(&mvpwm->controller.lock, flags);
+}
+
+static int mvebu_pwm_config(struct pwm_chip *chip, struct pwm_device *pwmd,
+			    int duty_ns, int period_ns)
+{
+	struct mvebu_pwm_chip_drv *chip_data = (struct mvebu_pwm_chip_drv*) pwmd->chip_data;
+	struct mvebu_pwmchip *controller = mvebu_pwm_list[chip_data->ctrl];
+	unsigned int on, off;
+	unsigned long long val;
+	unsigned long flags;
+
+	val = (unsigned long long) controller->clk_rate * duty_ns;
+	do_div(val, NSEC_PER_SEC);
+	if (val > UINT_MAX)
+		return -EINVAL;
+	if (val)
+		on = val;
+	else
+		on = 1;
+
+	val = (unsigned long long) controller->clk_rate * (period_ns - duty_ns);
+	do_div(val, NSEC_PER_SEC);
+	if (val > UINT_MAX)
+		return -EINVAL;
+	if (val)
+		off = val;
+	else
+		off = 1;
+
+	spin_lock_irqsave(&controller->lock, flags);
+	writel_relaxed(on, mvebu_pwmreg_blink_on_duration(controller));
+	writel_relaxed(off, mvebu_pwmreg_blink_off_duration(controller));
+	spin_unlock_irqrestore(&controller->lock, flags);
+
+	return 0;
+}
+
+static int mvebu_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct mvebu_pwm *mvpwm = to_mvebu_pwm(chip);
+	struct mvebu_gpio_chip *mvchip = mvpwm->mvchip;
+
+	mvebu_gpio_blink(&mvchip->chip, pwm->hwpwm, 1);
+
+	return 0;
+}
+
+static void mvebu_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct mvebu_pwm *mvpwm = to_mvebu_pwm(chip);
+	struct mvebu_gpio_chip *mvchip = mvpwm->mvchip;
+
+	mvebu_gpio_blink(&mvchip->chip, pwm->hwpwm, 0);
+}
+
+static const struct pwm_ops mvebu_pwm_ops = {
+	.request = mvebu_pwm_request,
+	.free = mvebu_pwm_free,
+	.config = mvebu_pwm_config,
+	.enable = mvebu_pwm_enable,
+	.disable = mvebu_pwm_disable,
+	.owner = THIS_MODULE,
+};
+
+static void mvebu_pwm_suspend(struct mvebu_gpio_chip *mvchip)
+{
+	struct mvebu_pwm *mvpwm = mvchip->mvpwm;
+
+	mvpwm->blink_select =
+		readl_relaxed(mvebu_gpioreg_blink_select(mvchip));
+	mvpwm->controller.blink_on_duration =
+		readl_relaxed(mvebu_pwmreg_blink_on_duration(&mvpwm->controller));
+	mvpwm->controller.blink_off_duration =
+		readl_relaxed(mvebu_pwmreg_blink_off_duration(&mvpwm->controller));
+}
+
+static void mvebu_pwm_resume(struct mvebu_gpio_chip *mvchip)
+{
+	struct mvebu_pwm *mvpwm = mvchip->mvpwm;
+
+	writel_relaxed(mvpwm->blink_select,
+		       mvebu_gpioreg_blink_select(mvchip));
+	writel_relaxed(mvpwm->controller.blink_on_duration,
+		       mvebu_pwmreg_blink_on_duration(&mvpwm->controller));
+	writel_relaxed(mvpwm->controller.blink_off_duration,
+		       mvebu_pwmreg_blink_off_duration(&mvpwm->controller));
+}
+
+/*
+ * Armada 370/XP has simple PWM support for gpio lines. Other SoCs
+ * don't have this hardware. So if we don't have the necessary
+ * resource, it is not an error.
+ */
+static int mvebu_pwm_probe(struct platform_device *pdev,
+		    struct mvebu_gpio_chip *mvchip,
+		    int id)
+{
+	struct device *dev = &pdev->dev;
+	struct mvebu_pwm *mvpwm;
+	struct resource *res;
+	u32 set;
+	enum mvebu_pwm_ctrl ctrl_set;
+
+	if (!of_device_is_compatible(mvchip->chip.of_node,
+				     "marvell,armada-370-gpio"))
+		return 0;
+
+	if (IS_ERR(mvchip->clk))
+		return PTR_ERR(mvchip->clk);
+
+	/*
+	 * There are only two sets of PWM configuration registers for
+	 * all the GPIO lines on those SoCs which this driver reserves
+	 * for the first two GPIO chips. So if the resource is missing
+	 * we can't treat it as an error.
+	 */
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pwm");
+	if (!res)
+		return 0;
+
+	/*
+	 * Use set A for lines of GPIO chip with id 0, B for GPIO chip
+	 * with id 1. Don't allow further GPIO chips to be used for PWM.
+	 */
+	if (id == 0) {
+		set = 0;
+		ctrl_set = MVEBU_PWM_CTRL_SET_A;
+	} else if (id == 1) {
+		set = U32_MAX;
+		ctrl_set = MVEBU_PWM_CTRL_SET_B;
+	} else {
+		return -EINVAL;
+	}
+	writel_relaxed(set, mvebu_gpioreg_blink_select(mvchip));
+
+	mvpwm = devm_kzalloc(dev, sizeof(struct mvebu_pwm), GFP_KERNEL);
+	if (!mvpwm)
+		return -ENOMEM;
+	mvchip->mvpwm = mvpwm;
+	mvpwm->mvchip = mvchip;
+
+	mvpwm->controller.membase = devm_ioremap_resource(dev, res);
+	if (IS_ERR(mvpwm->controller.membase))
+		return PTR_ERR(mvpwm->controller.membase);
+
+	mvpwm->controller.clk_rate = clk_get_rate(mvchip->clk);
+	if (!mvpwm->controller.clk_rate)
+		return -EINVAL;
+
+	mvpwm->chip.dev = dev;
+	mvpwm->chip.ops = &mvebu_pwm_ops;
+	mvpwm->chip.npwm = mvchip->chip.ngpio;
+
+	/*
+	 * There may already be some PWM allocated, so we can't force
+	 * mvpwm->chip.base to a fixed point like mvchip->chip.base.
+	 * So, we let pwmchip_add() do the numbering and take the next free
+	 * region.
+	 */
+	mvpwm->chip.base = -1;
+
+	spin_lock_init(&mvpwm->controller.lock);
+	mvpwm->default_counter = ctrl_set;
+	mvebu_pwm_list[ctrl_set] = &mvpwm->controller;
+
+	return pwmchip_add(&mvpwm->chip);
+}
+
 #ifdef CONFIG_DEBUG_FS
 #include <linux/seq_file.h>
 
@@ -561,6 +889,10 @@ static const struct of_device_id mvebu_gpio_of_match[] = {
 		.data	    = (void *) MVEBU_GPIO_SOC_VARIANT_ARMADAXP,
 	},
 	{
+		.compatible = "marvell,armada-370-gpio",
+		.data	    = (void *) MVEBU_GPIO_SOC_VARIANT_ORION,
+	},
+	{
 		/* sentinel */
 	},
 };
@@ -607,6 +939,9 @@ static int mvebu_gpio_suspend(struct platform_device *pdev, pm_message_t state)
 		BUG();
 	}
 
+	if (IS_ENABLED(CONFIG_PWM))
+		mvebu_pwm_suspend(mvchip);
+
 	return 0;
 }
 
@@ -650,6 +985,9 @@ static int mvebu_gpio_resume(struct platform_device *pdev)
 		BUG();
 	}
 
+	if (IS_ENABLED(CONFIG_PWM))
+		mvebu_pwm_resume(mvchip);
+
 	return 0;
 }
 
@@ -661,7 +999,6 @@ static int mvebu_gpio_probe(struct platform_device *pdev)
 	struct resource *res;
 	struct irq_chip_generic *gc;
 	struct irq_chip_type *ct;
-	struct clk *clk;
 	unsigned int ngpios;
 	unsigned int gpio_base = -1;
 	int soc_variant;
@@ -695,10 +1032,10 @@ static int mvebu_gpio_probe(struct platform_device *pdev)
 		return id;
 	}
 
-	clk = devm_clk_get(&pdev->dev, NULL);
+	mvchip->clk = devm_clk_get(&pdev->dev, NULL);
 	/* Not all SoCs require a clock.*/
-	if (!IS_ERR(clk))
-		clk_prepare_enable(clk);
+	if (!IS_ERR(mvchip->clk))
+		clk_prepare_enable(mvchip->clk);
 
 	mvchip->soc_variant = soc_variant;
 	mvchip->chip.label = dev_name(&pdev->dev);
@@ -835,6 +1172,10 @@ static int mvebu_gpio_probe(struct platform_device *pdev)
 		goto err_generic_chip;
 	}
 
+	/* Armada 370/XP has simple PWM support for GPIO lines */
+	if (IS_ENABLED(CONFIG_PWM))
+		return mvebu_pwm_probe(pdev, mvchip, id);
+
 	return 0;
 
 err_generic_chip:
